Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/santiagodc8/tu_perfil.net/llms.txt

Use this file to discover all available pages before exploring further.

TuPerfil.net organizes all content into six editorial categories. Each category has its own dedicated page, and readers can further narrow content by tag or by author.

The six categories

Perfil Político

Political news and analysis. URL: /perfil-politico

Perfil Judicial

Judiciary and legal affairs. URL: /perfil-judicial

Perfil Salud

Health and medicine. URL: /perfil-salud

Perfil Deportivo

Sports coverage. URL: /perfil-deportivo

Perfil Regional

Local and regional news. URL: /perfil-regional

Perfil Internacional

International news. URL: /perfil-internacional

Category page

URL pattern: /{category-slug} — for example, /perfil-politico The category page is a server component with ISR revalidation every 60 seconds.
src/app/(public)/[category]/page.tsx
export const revalidate = 60;
When a reader visits a category page, the following happens:
1

Category lookup

The slug is matched against the categories table. If no match is found, Next.js renders a 404 page via notFound().
2

Initial article batch

The first 12 articles (ARTICLES_PER_PAGE = 12) are fetched ordered by created_at descending. The total count is also returned so the UI knows whether more pages exist.
src/app/(public)/[category]/page.tsx
const { data: articles, count } = await supabase
  .from("articles")
  .select("title, slug, excerpt, image_url, created_at", { count: "exact" })
  .eq("published", true)
  .eq("category_id", category.id)
  .order("created_at", { ascending: false })
  .range(0, ARTICLES_PER_PAGE - 1)
  .returns<CategoryArticle[]>();
3

Render with breadcrumbs

The page renders a breadcrumb trail, the category name with its color accent bar, the article count, and the article grid via the LoadMoreArticles client component.

What the reader sees

  • Breadcrumb: Home › Category name
  • Category header: A colored vertical bar matching the category color, the category name as an <h1>, and the total article count (e.g., “42 noticias”).
  • Article grid: Cards displayed in a responsive grid. Each card shows the cover image, category badge, title, excerpt, and publication date.
  • Load more: If there are more than 12 articles, a “Cargar más” button appears at the bottom. Clicking it fetches the next 12 articles client-side and appends them to the grid — no page navigation needed.
  • Empty state: If a category has no published articles yet, the page shows the message “No hay noticias en esta categoría todavía.”

Category color usage

Each category has a color field (a hex value stored in the categories table). This color is used consistently across the site:
  • The vertical accent bar on the category header (style={{ backgroundColor: category.color }})
  • The category badge on each article card
  • The category badge inside the hero carousel slide overlay
  • The trending section category label

SEO and Open Graph

Category pages generate dynamic <head> metadata via generateMetadata:
src/app/(public)/[category]/page.tsx
export async function generateMetadata({ params }) {
  const { data: category } = await supabase
    .from("categories")
    .select("name")
    .eq("slug", params.category)
    .single();

  return {
    title: category.name,
    description: `Noticias de ${category.name} en TuPerfil.net`,
    openGraph: { title: category.name, description: `...` },
    twitter: { card: "summary_large_image" },
  };
}
A dynamic Open Graph image is generated automatically by opengraph-image.tsx in the same directory.

Tag pages

URL pattern: /etiqueta/{tag-slug} — for example, /etiqueta/elecciones-2025 Tag pages let readers explore all articles that share a specific editorial tag. Tags are assigned to articles by editors in the admin panel. The tag page fetches articles by joining through the article_tags pivot table:
src/app/(public)/etiqueta/[slug]/page.tsx
const { data: rows, count } = await supabase
  .from("article_tags")
  .select(
    "article:articles(title, slug, excerpt, image_url, created_at, category:categories(name, color))",
    { count: "exact" }
  )
  .eq("tag_id", tag.id)
  .eq("article.published", true)
  .order("article_id", { ascending: false })
  .range(0, ARTICLES_PER_PAGE - 1)
  .returns<TagArticleRow[]>();
The page header shows #{tag.name} and the total article count. Up to 12 articles are displayed in a 3-column grid. If there are more than 12 articles, a count notice appears (e.g., “Mostrando 12 de 35 noticias”). Tag links appear on individual article pages as pill buttons below the article body:
src/app/(public)/noticia/[slug]/page.tsx
<Link href={`/etiqueta/${tag.slug}`}>
  #{tag.name}
</Link>

Author pages

URL pattern: /autor/{author-name-slug} — for example, /autor/maria-gonzalez Author pages aggregate all published articles written by a given author. The slug is derived from the author’s name using the generateSlug utility. The lookup is case-insensitive:
src/app/(public)/autor/[slug]/page.tsx
const { data: articles, count } = await supabase
  .from("articles")
  .select("title, slug, excerpt, image_url, created_at, category:categories(name, color)", {
    count: "exact",
  })
  .eq("published", true)
  .ilike("author_name", name)
  .order("created_at", { ascending: false })
  .limit(50)
  .returns<AuthorArticle[]>();
If no articles are found for the given author slug, the page returns a 404. The display name is taken from the first matching article’s author_name field (preserving the original capitalization). Author links appear on article pages next to the byline:
src/app/(public)/noticia/[slug]/page.tsx
<Link href={`/autor/${generateSlug(article.author_name)}`}>
  {article.author_name}
</Link>
Author pages show up to 50 articles. There is no pagination on author pages — the 50 most recent articles by that author are always shown.